package cn.edu.pku.nggis.remotesensingdigitalimageprocessing;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.omg.CORBA.PRIVATE_MEMBER;

import sun.applet.Main;

public class RadialBasisFunctionClassifyProcess extends
		MinimalDistanceClassifyProcess implements ClassifyProcess {

	public static double STEPSIZE = 0.0001;
	public static double ZEROLIMIT = 1e-10;

	protected class DoubleArrayPointer {
		public double[] a;

		public DoubleArrayPointer(double[] a) {
			this.a = a;
		}
	}

	private Map<Point, DoubleArrayPointer> RBFTempMap = new HashMap<Point, DoubleArrayPointer>();

	@Override
	public void process(Map<Integer, List<Point>> samplePixels,
			int[][] classifiedArray, int[][][] dataArray,
			Map<String, Object> params) {
		double[][] weightNet = getInitialWeightNet();
		List<muRecord> muList = getMuList(samplePixels, dataArray);
		Collections.sort(muList);
		double[] sigmas = getSigmas(samplePixels, dataArray, muList);

		adjustWeightNet(weightNet, dataArray, muList, samplePixels, sigmas);

		classify(classifiedArray, weightNet, dataArray, muList, sigmas);
	}

	private void classify(int[][] classifiedArray, double[][] weightNet,
			int[][][] dataArray, List<muRecord> muList, double[] sigmas) {
		for (int x = 0; x < MainProcess.WIDTH; x++) {
			for (int y = 0; y < MainProcess.HEIGHT; y++) {
				double[] RBFOutputs = getRBFOutputs(dataArray[x][y], muList,
						sigmas);
				double[] finalOutputs = getFinalOutputs(RBFOutputs, weightNet,
						dataArray[x][y], muList, sigmas);
				classifiedArray[x][y] = muList.get(seekMax(finalOutputs)).rgb;
			}
		}
	}

	private int seekMax(double[] array) {
		int index = 0;
		double max = Double.MIN_VALUE;
		for (int i = 0; i < array.length; i++) {
			if (array[i] > max) {
				max = array[i];
				index = i;
			}
		}
		return index;
	}

	private void adjustWeightNet(double[][] weightNet, int[][][] dataArray,
			List<muRecord> muList, Map<Integer, List<Point>> samplePixels,
			double[] sigmas) {
		int time = 0;
		double EBefore = 0;
		double EAfter = getE(weightNet, dataArray, muList, samplePixels, sigmas);
		while (Math.abs(EBefore - EAfter) > ZEROLIMIT) {
			adjustWeightNetOnce(weightNet, dataArray, muList, samplePixels,
					sigmas);
			EBefore = EAfter;
			EAfter = getE(weightNet, dataArray, muList, samplePixels, sigmas);
			time++;
			System.out.println(time + "th time, E is " + EAfter + ".");
		}
	}

	private double getE(double[][] weightNet, int[][][] dataArray,
			List<muRecord> muList, Map<Integer, List<Point>> samplePixels,
			double[] sigmas) {
		double E = 0;
		int samplesNum = 0;
		for (int i = 0; i < MainProcess.CLASSNUM; i++) {
			List<Point> pl = samplePixels.get(muList.get(i).rgb);
			samplesNum += pl.size();
			for (Point p : pl) {
				double[] RBFOutputs = getRBFOutputsWithMap(dataArray, muList,
						sigmas, p);
				double[] finalOutputs = getFinalOutputs(RBFOutputs, weightNet,
						dataArray[p.x][p.y], muList, sigmas);
				for (int j = 0; j < finalOutputs.length; j++) {
					if (i == j) {
						double d = finalOutputs[j] - 1;
						E += d * d;
					} else {
						E += finalOutputs[j] * finalOutputs[j];
					}
				}
			}
		}
		E = E / (2 * samplesNum);
		return E;
	}

	private void adjustWeightNetOnce(double[][] weightNet, int[][][] dataArray,
			List<muRecord> muList, Map<Integer, List<Point>> samplePixels,
			double[] sigmas) {
		for (int i = 0; i < MainProcess.CLASSNUM; i++) {
			List<Point> pl = samplePixels.get(muList.get(i).rgb);
			for (Point p : pl) {
				double[] RBFOutputs = getRBFOutputsWithMap(dataArray, muList,
						sigmas, p);
				double[] finalOutputs = getFinalOutputs(RBFOutputs, weightNet,
						dataArray[p.x][p.y], muList, sigmas);
				for (int v = 0; v < finalOutputs.length; v++) {
					double deltav = finalOutputs[v];
					if (i == v) {
						deltav = deltav - 1;
					}
					for (int u = 0; u < RBFOutputs.length; u++) {
						weightNet[u][v] = weightNet[u][v] - STEPSIZE * deltav
								* RBFOutputs[u];
					}
				}
			}
		}
	}

	private double[] getRBFOutputsWithMap(int[][][] dataArray,
			List<muRecord> muList, double[] sigmas, Point p) {
		double[] RBFOutputs;
		if (RBFTempMap.containsKey(p)) {
			RBFOutputs = RBFTempMap.get(p).a;
		} else {
			RBFOutputs = getRBFOutputs(dataArray[p.x][p.y], muList, sigmas);
			RBFTempMap.put(p, new DoubleArrayPointer(RBFOutputs));
		}
		return RBFOutputs;
	}

	private double[] getRBFOutputs(int[] datas, List<muRecord> muList,
			double[] sigmas) {
		double[] RBFOutputs = new double[MainProcess.CLASSNUM];
		for (int i = 0; i < RBFOutputs.length; i++) {
			RBFOutputs[i] = getRBFOutput(datas, muList.get(i).samples,
					sigmas[i]);
		}
		return RBFOutputs;
	}

	private double[] getFinalOutputs(double[] RBFOutputs, double[][] weightNet,
			int[] datas, List<muRecord> muList, double[] sigmas) {
		double[] finalOutputs = new double[MainProcess.CLASSNUM];
		for (int i = 0; i < finalOutputs.length; i++) {
			for (int j = 0; j < RBFOutputs.length; j++) {
				finalOutputs[i] += weightNet[j][i] * RBFOutputs[j];
			}
		}

		return finalOutputs;
	}

	private double[][] getInitialWeightNet() {
		double[][] weightNet = new double[MainProcess.CLASSNUM][MainProcess.CLASSNUM];
		for (int i = 0; i < MainProcess.CLASSNUM; i++) {
			for (int j = 0; j < MainProcess.CLASSNUM; j++) {
				weightNet[i][j] = 1;
			}
		}
		return weightNet;
	}

	private double getRBFOutput(int[] datas, long[] samples, double sigma) {
		double result = 0;
		for (int i = 0; i < MainProcess.BANDNUM; i++) {
			long d = datas[i] - samples[i];
			result += d * d;
		}
		result = Math.exp(-result / (2 * sigma));
		return result;
	}

	private double[] getSigmas(Map<Integer, List<Point>> samplePixels,
			int[][][] dataArray, List<muRecord> muList) {
		double[] sigma = new double[MainProcess.CLASSNUM];

		for (int i = 0; i < MainProcess.CLASSNUM; i++) {
			List<Point> pl = samplePixels.get(muList.get(i).rgb);
			long[] samples = muList.get(i).samples;

			for (Point p : pl) {
				for (int j = 0; j < MainProcess.BANDNUM; j++) {
					long d = samples[j] - dataArray[p.x][p.y][j];
					sigma[i] += d * d;
				}
			}

			sigma[i] = sigma[i] / pl.size();
		}

		return sigma;
	}

}
